package com.hp.bon.sgw.ctrl;

import java.io.File;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.apache.http.HttpVersion;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.params.ConnManagerParams;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.scheme.SocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpProtocolParams;
import org.jboss.resteasy.client.ClientExecutor;
import org.jboss.resteasy.client.ProxyFactory;
import org.jboss.resteasy.client.core.executors.ApacheHttpClient4Executor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import com.google.gson.Gson;
import com.hp.bon.sgw.bean.SysPertriNetBean;
import com.hp.bon.sgw.ctrl.SGWController.NodeProcessInf;
import com.hp.bon.sgw.service.ConfigService;
import com.hp.bon.sgw.service.OmcRestService;
import com.hp.bon.sgw.service.SyslogFilterEventHandler;

/**
 * 负责Petri网状态切换和进程起停控制
 * 
 * @author wuzhih
 * 
 */
@Path("/")
public class PetriExecutor {
	/**
	 * Logger for this class
	 */
	private static final Logger logger = LoggerFactory.getLogger(PetriExecutor.class);
	// /**
	// * 本机所有IP地址
	// */
	// private Set<String> localIps;

	protected volatile boolean needStop;
	protected SystemPlantformIntf sysPlantform;
	protected final SysPertriNetBean pertriBean = new SysPertriNetBean(true);
	private String startFlag = null;
	private String statusFile;
	private ClientExecutor clientExecutor;

	@GET
	@Path("sysctrl/alive")
	@Produces(MediaType.TEXT_PLAIN)
	public int checkNodeAlive() {
		return pertriBean.getStatus();
	}

	@GET
	@Path("sgwstat/getModuleList/{node}")
	@Produces(MediaType.APPLICATION_JSON)
	public String getModuleList(@PathParam("node") String node) {
		LinkedList<String> procNames = new LinkedList<String>();
		for (NodeProcessInf nodeProc : ControllerConfigService.getInstance().getSgwInstanceMap().values()) {
			procNames.add(nodeProc.id);
		}
		return new Gson().toJson(procNames);
	}

	@GET
	@Path("system/sysstatus")
	@Produces(MediaType.TEXT_PLAIN)
	public String checkAllNodeStatus() {
		StringBuilder sb = new StringBuilder();
		for (NodeProcessInf nodeProc : ControllerConfigService.getInstance().getSgwInstanceMap().values()) {
			int status = checkNodeStatus(nodeProc, true);
			sb.append(nodeProc.toString() + " ,status=" + SysPertriNetBean.getPertiStatusName(status)).append("\r\n");
		}
		return sb.toString();
	}

	@GET
	@Path("sgwinit/action/{action}/{node}")
	@Produces(MediaType.APPLICATION_JSON)
	public String doInitAction(@PathParam("action") String action, @PathParam("node") String node) {
		try {
			if (action.equalsIgnoreCase("s")) {
				return "Bad action, System is now in status of " + SysPertriNetBean.getPertiStatusName(checkNodeAlive());
			} else if (action.equalsIgnoreCase("v")) {
				return doModeChange("m", node);
			} else {
				return "not recoginized action" + action;
			}
		} catch (Exception e) {
			return "error: exception " + e.toString();
		}
	}

	@GET
	@Path("sgwmode/doModeChange/{action}/{node}")
	@Produces(MediaType.APPLICATION_JSON)
	public String doModeChange(@PathParam("action") String action, @PathParam("node") String node) {
		int newStatus = 0;
		boolean force = false;
		try {
			if ("k".equalsIgnoreCase(action)) {// 切换状态到offline
				newStatus = SysPertriNetBean.PETRI_STATUS_OFFLINE;
			} else if ("ki".equalsIgnoreCase(action)) {// 切换状态到offline, 强制
				pertriBean.setNewStatus(SysPertriNetBean.PETRI_STATUS_PREPARE_OFFLINE);
				forceKillNodes();
				return "please standby ,system will force shutdown ";
			} else if ("s".equalsIgnoreCase(action)) {// 切换状态到maintain
				newStatus = SysPertriNetBean.PETRI_STATUS_MAINTAIN;
			} else if ("m".equalsIgnoreCase(action)) {// 切换状态到online
				newStatus = SysPertriNetBean.PETRI_STATUS_ONLINE_OFFNET;
			} else if ("u".equalsIgnoreCase(action)) {// 切换状态到maintain, 强制
				newStatus = SysPertriNetBean.PETRI_STATUS_MAINTAIN;
				force = true;
			} else if ("r".equalsIgnoreCase(action)) {// BON网注册
				newStatus = SysPertriNetBean.PETRI_STATUS_ONLINE_ONNET;
			} else if ("d".equalsIgnoreCase(action)) {// BON网注销
				newStatus = SysPertriNetBean.PETRI_STATUS_ONLINE_OFFNET;
			} else {
				return "error: can't recoginized action" + action;
			}
			return doSysPetriChange(newStatus, force);
		} catch (Exception e) {
			e.fillInStackTrace();
			logger.warn("exception " + e);
			return "error: exception " + e.toString();
		}
	}

	private void alarmSgwStateNotiry(String status) {
		logger.warn("Send alarm sgw state notify to OMC.[status=" + status + "]");
		try {
			OmcRestService.getInstance().getOMCAlarmInterface().alarmSGWStateNotify(ConfigService.sgwHostId, status, String.valueOf(System.currentTimeMillis()));
		} catch (Exception e) {
			logger.warn("Send alarm sgw state notify to OMC failed:" + e.getMessage(), e);
		}
	}

	private String doSysPetriChange(int newStatus, boolean force) {
		if (!this.pertriBean.switchStatus(newStatus)) {
			String msg = "can't switch from " + this.pertriBean.getPertiStatusName() + " to " + SysPertriNetBean.getPertiStatusName(newStatus);
			logger.info(msg);
			return msg;
		}
		if (newStatus == -3) {
			if (force) {
				this.pertriBean.setNewStatus(-3);
				forceKillNodes();
			} else {
				this.pertriBean.setNewStatus(-4);
				changeSystemToMaintain();
			}
			alarmSgwStateNotiry("2");
		} else {
			if (newStatus == 0) {
				String info = ControllerConfigService.getInstance().getSgwRemoteServer().doSysPetriChange(newStatus);
				logger.info(info);
				this.pertriBean.setNewStatus(ControllerConfigService.getInstance().getSgwRemoteServer().checkNodeAlive());
				alarmSgwStateNotiry("1");
				return info;
			}
			if (newStatus == -2) {
				if (this.pertriBean.getStatus() == -3) {
				// 启动进程
					changeSystemToOnline();
				} else {
					String info = ControllerConfigService.getInstance().getSgwRemoteServer().doSysPetriChange(newStatus);
					this.pertriBean.setNewStatus(ControllerConfigService.getInstance().getSgwRemoteServer().checkNodeAlive());
					logger.info(info);
					return info;
				}
				this.pertriBean.setNewStatus(newStatus);
				alarmSgwStateNotiry("0");
			} else if (newStatus == -1) {
				this.pertriBean.setNewStatus(-6);
				if (force)
					forceKillNodes();
				else {
					changeSystemToMaintain();
				}
				alarmSgwStateNotiry("0");
			}
		}
		return "request is accepted successful,please standby for system to finish task...";
	}

	private void setupHttpClient() {
		BasicHttpParams params = new BasicHttpParams();
		ConnManagerParams.setMaxTotalConnections(params, 100);
		HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
		// HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_0);
		HttpConnectionParams.setConnectionTimeout(params, 3000);
		HttpConnectionParams.setSoTimeout(params, 10000);
		SchemeRegistry schemeRegistry = new SchemeRegistry();
		SocketFactory sf = PlainSocketFactory.getSocketFactory();
		schemeRegistry.register(new Scheme("http", sf, 80));
		ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);
		DefaultHttpClient httpclient = new DefaultHttpClient(cm, params);
		clientExecutor = new ApacheHttpClient4Executor(httpclient);
	}

	public void start() throws SocketException {

		// 删除状态文件
		File statFile = new File(statusFile);
		if (statFile.exists()) {
			statFile.delete();
		}
		setupHttpClient();

		// this.localIps = Comutils.getLocalhostAllIps();
		startFlag = System.getProperty("startFlag");
		logger.info("start flag is " + startFlag);

		if ("s".equals(startFlag)) {
			// maitain
			return;
		} else if ("v".equals(startFlag)) {
			// online offnet
			changeSystemToOnline();
			this.getPertriBean().setNewStatus(SysPertriNetBean.PETRI_STATUS_ONLINE_OFFNET);
		} else if ("o".equals(startFlag)) {
			// online onnet
			changeSystemToOnline();
			this.getPertriBean().setNewStatus(SysPertriNetBean.PETRI_STATUS_ONLINE_OFFNET);
			try {
				// 等待启动进程完成
				Thread.sleep(15000L);
				String info = doSysPetriChange(0, false);
				logger.info(info);
			} catch (Exception e) {
				e.printStackTrace();
				logger.warn("start to online err:" + e);
			}
		}
	}

	public SGWRemoteInterface getRemoetIntf(String nodeURL) {
		return ProxyFactory.create(SGWRemoteInterface.class, nodeURL, clientExecutor);
	}

	public int checkNodeStatus(NodeProcessInf nodeProc, boolean checkProcessExist) {
		int status = SysPertriNetBean.PETRI_STATUS_OFFLINE;
		try {
			status = getRemoetIntf(nodeProc.managerURL).checkNodeAlive();
		} catch (Exception e) {
			//
		}
		nodeProc.setStatus(status);
		return status;
	}

	private void sendStatusChangeCmdToNode(NodeProcessInf procInf, int newStatus) {
		try {
			getRemoetIntf(procInf.managerURL).doSysPetriChange(newStatus);
		} catch (Exception e) {
			logger.info("remote do mode change err " + e.toString());
		}
	}

	private void forceKillNodes() {
		logger.info("force change system status to MAINTAINANCE ....");
		Collection<NodeProcessInf> processNodes = ControllerConfigService.getInstance().getSgwInstanceMap().values();
		List<String> nodeProcPatterns = new LinkedList<String>();
		for (NodeProcessInf procInf : processNodes) {
			String procPattern = "-DnodeID=" + procInf.id + " ";
			System.out.println(procPattern);
			nodeProcPatterns.add(procPattern);
		}
		try {
			long[] procExists = sysPlantform.checkProcessExists(nodeProcPatterns.toArray(new String[nodeProcPatterns.size()]));
			for (long pid : procExists) {
				try {
					if (pid != 0) {
						logger.info("kill process ,pid:" + pid);
						sysPlantform.shutdownProcess(pid);
					}
				} catch (Exception e) {
					logger.warn("kill process err " + e.toString());
				}
			}
		} catch (Exception e) {
			logger.warn("caught exception " + e.toString());
		}

	}

	private void changeSystemToMaintain() {
		logger.info("prepare to change system status to MAINTAINANCE ....");
		Collection<NodeProcessInf> processNodes = ControllerConfigService.getInstance().getSgwInstanceMap().values();
		List<String> nodeProcPatterns = new LinkedList<String>();
		for (NodeProcessInf procInf : processNodes) {
			int status = checkNodeStatus(procInf, false);
			if (status != SysPertriNetBean.PETRI_STATUS_OFFLINE) {
				logger.info("send status command to node " + procInf.id + " ,cur status " + SysPertriNetBean.getPertiStatusName(status));
				sendStatusChangeCmdToNode(procInf, SysPertriNetBean.PETRI_STATUS_MAINTAIN);
			} else {// kill process for safe
				nodeProcPatterns.add("-DnodeID=" + procInf.id);
			}
		}
		try {
			long[] procExists = sysPlantform.checkProcessExists(nodeProcPatterns.toArray(new String[nodeProcPatterns.size()]));
			for (long pid : procExists) {
				try {
					if (pid != 0) {
						sysPlantform.shutdownProcess(pid);

					}
				} catch (Exception e) {
					logger.warn("kill process err " + e.toString());
				}
			}
		} catch (Exception e) {
			logger.warn("caught exception " + e.toString());
		}

	}

	public void changeSystemToOnline() {
		logger.info("prepare to change system status to ONLINE ....");
		List<Long> deadNodes = new LinkedList<Long>();
		Collection<NodeProcessInf> offLineNodes = new LinkedList<NodeProcessInf>();
		Collection<NodeProcessInf> processNodes = ControllerConfigService.getInstance().getSgwInstanceMap().values();
		List<String> nodeProcPatterns = new LinkedList<String>();
		for (NodeProcessInf procInf : processNodes) {
			int status = checkNodeStatus(procInf, false);
			if (status == SysPertriNetBean.PETRI_STATUS_OFFLINE) {
				offLineNodes.add(procInf);
				nodeProcPatterns.add("-DnodeID=" + procInf.id);
			}
			logger.info("finished check node status ,node " + procInf.id + " status " + SysPertriNetBean.getPertiStatusName(status));
		}
		logger.info("check if exists dead node's process....");
		// 检查deadNodes
		try {
			long[] procExists = sysPlantform.checkProcessExists(nodeProcPatterns.toArray(new String[nodeProcPatterns.size()]));
			int i = 0;
			for (NodeProcessInf procInf : offLineNodes) {
				long pid = procExists[i++];
				if (pid != 0) {
					deadNodes.add(pid);
					logger.info("node is dead ,later will be killed and reboot " + procInf.toString());
				}

			}

		} catch (Exception e) {
			logger.warn("caught exception " + e.toString());
		}
		// kill 僵死的节点
		for (long pid : deadNodes) {
			try {
				sysPlantform.shutdownProcess(pid);
			} catch (Exception e) {
				logger.warn("kill process err " + e.toString());
			}
		}
		logger.info("start process.....");
		// 启动所有节点
		for (NodeProcessInf procInf : processNodes) {
			if (procInf.status != SysPertriNetBean.PETRI_STATUS_OFFLINE) {
				logger.info("skip alive process" + procInf.toString() + "," + SysPertriNetBean.getPertiStatusName(procInf.status));
				continue;
			}
			boolean suc = false;
			try {
				suc = sysPlantform.startProcess(getNodeStartCmd(procInf));
			} catch (Exception e) {
				logger.warn("error " + e.toString());
			}
			logger.info("start process " + procInf.toString() + " success " + suc);
		}
		logger.info("start process finished ");
	}

	protected String[] getNodeStartCmd(NodeProcessInf inf) {
		List<String> args = new ArrayList<String>(3);
		args.add((String) System.getenv().get("SGW_HOME") + File.separator + "bin" + File.separator + inf.cmd);
		args.add(inf.id);
		return (String[]) args.toArray(new String[args.size()]);
	}

	@Autowired
	public void setSysLogFilterHandler(SyslogFilterEventHandler sysLogFilterHandler) {
		sysLogFilterHandler.setPetriExecutor(this);
	}

	@Autowired
	public void setSysPlantform(SystemPlantformIntf sysPlantform) {
		this.sysPlantform = sysPlantform;
	}

	public boolean needStop() {
		return needStop;
	}

	public SysPertriNetBean getPertriBean() {
		return pertriBean;
	}

	public void setStartFlag(String startFlag) {
		this.startFlag = startFlag;
	}

	public String getStatusFile() {
		return statusFile;
	}

	public void setStatusFile(String statusFile) {
		this.statusFile = statusFile;
	}

	public void onCongestionLevelChanged(int oldLevel, int newLevel) {
		logger.warn("found system congestion level changed ,notify nodes ");

		for (NodeProcessInf nodeProc : ControllerConfigService.getInstance().getSgwInstanceMap().values()) {
			if (nodeProc.status != SysPertriNetBean.PETRI_STATUS_OFFLINE) {
				if ("CTP".equals(nodeProc.processType)) {
					String info = "notify " + nodeProc.toString() + " ,congestion level changed to " + newLevel;
					try {
						String result = getRemoetIntf(nodeProc.managerURL).doCongestionChanged(newLevel);
						info += " " + result;
					} catch (Exception e) {
						info += " " + e;
					} finally {
						logger.warn(info);
					}
				}
			}
		}
	}
}